﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CardGamesLibrary.Cards;
using CardGamesLibrary.Tables;
using CardGamesLibrary.Turns;
using System.Threading;

namespace CardGamesLibrary.Poker
{
    public class PokerGameTurn : GameTurn
    {
        private readonly PokerTable _table;

        public PokerGameTurn(PokerTable table) : base(table.Seats)
        {
            _table = table;
        }

        private int i = 0;
        public override void Play()
        {
            switch (i)
            {
                // Deal
                case 0:

                    _table.Turn = new TexasHoldem.InitTexasHoldemTableTurn(_table);
                    
                    break;


                // Flop
                case 1:
                    _table.Turn = new TexasHoldem.FlopTexasHoldemTableTurn(_table);
                   
                   break;
                
                // Turn & River
                case 2:
                case 3:
                  _table.Turn = new TexasHoldem.TurnTexasHoldemTableTurn(_table);
                     break;
            }
          
            _table.Turn.Play();
            Thread.Sleep(200);
            i++;
            if (i == 4)
                Finished = true;
        }

        public override TableSeat GetWinner()
        {
            // compare the hand of all the players around the table and return the highest one.
            IDictionary<Hand<FrenchDeckCard>,int> scores = new Dictionary<Hand<FrenchDeckCard>, int>();
            var hand = _table.Seats
                .Where(a => a.Player != null).ToDictionary(a=>a,a=> GetBestHand(a.Hand, _table.Cards, 5)).OrderBy(a=>a.Value);
            if(hand.Count()==0)
                throw new  NotImplementedException();

            var player = hand.First();
            Console.WriteLine(player.Key.Player.Name + " Wins");
            Console.WriteLine("With {0} > {1}", Enum.GetName(typeof(PokerHandResult), player.Value.Result), player.Value.Of.First());

            return hand.First().Key;
        }

        public static PokerHandValue GetBestHand(ICardSet<FrenchDeckCard> hand, ICardSet<FrenchDeckCard> cards, int maxCards)
        {
            var list = new List<FrenchDeckCard>();
            list.AddRange(hand);
            list.AddRange(cards);

            if(list.Count<maxCards)
                throw new NotImplementedException();

           // in texas foldem there are 7 cards a the end of the game
           // but winning hand must be taken from 5 cards so: 
           // 7*6*5*4*3, possible hands are : 2520, but since the order does not matter
           // 2520 / (5!) * (2!) = 2520 / 240 = 10.5 or 11 possible hands are to be analyzed.
           // this gives 2520 possible hands. we still have an overhead of 240 times in iterations but it gets us the results
            var cominations = GetCombinations(list, new List<List<FrenchDeckCard>>(), 5).ToDictionary(a => a,a=> GetHandResult(a)).OrderBy(a => a.Value);
            foreach (var combination in cominations)
            {
                return combination.Value;
            }

            throw new NotImplementedException();

        }

        public static PokerHandValue GetHandResult(List<FrenchDeckCard> combination)
        {
            // to reduce the overhead of the checks and avoid repetive Distinct() calls etc
            // we route the type of hand based on some quick checks.

            var suites = combination.Select(a => a.Suite).Distinct();
            var ranks = combination.Select(a => a.Rank).Distinct();
            var remainingRanks = combination.Select(a => a.Rank).Except(ranks);
            var remainingRanksCount = remainingRanks.Count();
            var suitesCount = suites.Count();
            var ranksCount = ranks.Count();

            var cardCounts = new Dictionary<Rank, int>();

            foreach (var item in combination.Select(a=>a.Rank).Distinct())
            {
              cardCounts.Add(item,combination.Count(c=>c.Rank==item));   
            }

            if(suitesCount == 1)
            {
                var straigth = IsStraigth(combination);
                return new PokerHandValue(straigth ? PokerHandResult.StraightFlush : PokerHandResult.Flush, combination.OrderByDescending(a => a.Rank).First());
                // evaluate flushes
            }
            else if(ranksCount < combination.Count)
            {
                // evaluate pairs, threeof a kind, quads, Full house
                if(ranksCount == 4)
                {
                    // 4 distinct card ranks(values) of 5, you have pair
                    return new PokerHandValue(PokerHandResult.Pair, combination.Where(a => a.Rank == cardCounts.SingleOrDefault(b => b.Value == 2).Key).First());
                }
                
                if(ranksCount == 3)
                {

                 

                    // 3 distinct card ranks(values) of 5, you might have two pairs or three of a kind
                    if(cardCounts.Any(a=>a.Value == 3))
                        return new PokerHandValue(PokerHandResult.ThreeOfAKind, combination.Where(a=>a.Rank == cardCounts.SingleOrDefault(b=>b.Value==3).Key).First()); 
                    
                    
                    if (cardCounts.Count(a => a.Value == 2)==2)
                        return new PokerHandValue(PokerHandResult.TwoPair, combination.Where(a => cardCounts.Where(b => b.Value == 2).Select(b=>b.Key).Contains(a.Rank)).ToArray());
                }
                if(ranksCount == 2)
                {
                    // 2 distinct card ranks(values) of 5, you might have a full house or four of a kind
                    if (cardCounts.Count(a => a.Value == 4) == 1)
                        return new PokerHandValue(PokerHandResult.FourOfKind, combination.Where(a => a.Rank == cardCounts.SingleOrDefault(b => b.Value == 4).Key).First());
                    if (cardCounts.Count(a => a.Value == 3) == 1 && cardCounts.Count(a => a.Value == 2)==1)
                        return new PokerHandValue(PokerHandResult.FullHouse, combination.Where(a => a.Rank == cardCounts.SingleOrDefault(b => b.Value == 3).Key).First(), combination.Where(a => a.Rank == cardCounts.SingleOrDefault(b => b.Value == 2).Key).First());
                    
                }
            }
            else if(ranksCount == combination.Count)
            {
                var straigth = IsStraigth(combination);

                if(straigth)
                    return new PokerHandValue(PokerHandResult.Straight, combination.OrderByDescending(a => a.Rank).First());
                // check straights and highcard.
            }
            return  new PokerHandValue( PokerHandResult.HighCard, combination.OrderByDescending(a=>a.Rank).First());
        }

        private static bool IsStraigth(IEnumerable<FrenchDeckCard> combination)
        {
            var ordered = combination.OfType<FrenchDeckCard>().OrderBy(a => a.Rank);
            var first = ordered.First();
            int count = 0;
            foreach (var frenchDeckCard in ordered)
            {
                if (frenchDeckCard.Rank - first.Rank - count != 0)
                    return false;
                count++;
            }
            return count == 5;
        }

        public static List<List<FrenchDeckCard>> GetCombinations(IEnumerable<FrenchDeckCard> list, List<List<FrenchDeckCard>> results, int stopAt)
        {
            if (results.Count == 0)
            {
                // in the first loop populate the results with the each card once
                results.AddRange(list.Select(frenchDeckCard => new List<FrenchDeckCard>(stopAt) { frenchDeckCard }));
            }
            else
            {
                results = (from result in results
                                   /* let the remaining cards be everything except the ones in the result we evaluate */
                                  let remainingCards = list.Except(result)
                                  from frenchDeckCard in remainingCards
                                  // Make a new list that combines the old result and each possibility
                                  select new List<FrenchDeckCard>(result) {frenchDeckCard}).ToList();
                // strip the results
                results = new List<List<FrenchDeckCard>>(results.Distinct(new CardsListComparer()));

            }
            // if we have enough cards in each result, quit the loop.
            if(results.Count > 0 && results.First() != null && results.First().Count==stopAt)
                return results;

            // watch out this is recusrive can lead to endless loop
            return GetCombinations(list, results, stopAt);
        }
    }

    internal class CardsListComparer : IEqualityComparer<List<FrenchDeckCard>>
    {
        public bool Equals(List<FrenchDeckCard> x, List<FrenchDeckCard> y)
        {
            return GetHashCode(x) == GetHashCode(y);
        }

        public int GetHashCode(List<FrenchDeckCard> obj)
        {
            return obj.Aggregate(0, (current, frenchDeckCard) => current ^ frenchDeckCard.Rank.GetHashCode());
        }
    }

    public class PokerHandValue : IComparable<PokerHandValue>
    {
        public PokerHandValue(PokerHandResult result,params FrenchDeckCard[] ofCards )
        {
            Result = result;
            Of = ofCards;
        }
        public PokerHandResult Result { get; set; }
        public FrenchDeckCard[] Of { get; set; }


        #region IComparable<PokerHandValue> Members

        public int CompareTo(PokerHandValue other)
        {
            if (other.Result.Equals(this.Result))
            {
                return this.Of[0].Rank.CompareTo(other.Of[0].Rank);
            }
            else {
                return this.Result.CompareTo(other.Result);
            }
        }

        #endregion
    }
    public enum PokerHandResult
    {
        StraightFlush,
        FourOfKind,
        FullHouse,
        Flush,
        Straight,
        ThreeOfAKind,
        TwoPair,
        Pair,
        HighCard
    }
}
